/*
 * Bump in the Host (BIH) 
 * http://code.google.com/p/bump-in-the-host/source/
 * ----------------------------------------------------------
 *
 *  Copyrighted (C) 2010,2011 by the China Mobile Communications 
 *  Corporation <bih.cmcc@gmail.com>;
 *  See the COPYRIGHT file for full details. 
 *
 *  This program is free software; you can redistribute it and/or modify
 *  it under the terms of the GNU General Public License as published by
 *  the Free Software Foundation; either version 2 of the License, or
 *  (at your option) any later version.
 *
 *  This program is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *  GNU General Public License for more details.
 *
 *  You should have received a copy of the GNU General Public License
 *  along with this program; if not, write to the Free Software
 *  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111, USA.
 *
 */

#include <linux/version.h>
#include <linux/sched.h>
#include <linux/kernel.h>       /* printk() */
#include <linux/slab.h>
#include <linux/errno.h>        /* error codes */
#include <linux/types.h>        /* size_t */
#include <linux/interrupt.h>    /* mark_bh */
#include <linux/random.h>
#include <linux/in.h>
#include <linux/netdevice.h>    /* struct device, and other headers */
#include <linux/etherdevice.h>  /* eth_type_trans */
#include <linux/inetdevice.h>
#include <net/ip.h>             /* struct iphdr */
#include <net/icmp.h>           /* struct icmphdr */
#include <net/ipv6.h>
#include <net/udp.h>
#include <linux/skbuff.h>
#include <linux/in6.h>
#include <linux/init.h>
#include <asm/uaccess.h>
#include <asm/checksum.h>
#include <net/addrconf.h>
#include "fanyi.h"
#include "map.h"
#include "pool.h"
#include "voslist.h"
#define NIPQUAD(addr) \
	((unsigned char *)&addr)[0], \
	((unsigned char *)&addr)[1], \
	((unsigned char *)&addr)[2], \
	((unsigned char *)&addr)[3]
#define __BIHDEBUG__
/*
 * If tos_ignore_flag != 0, we don't copy TOS and Traffic Class
 * from origin paket and set it to 0
 */
int tos_ignore_flag = 0;
int BIHMODE,NETWORKTYPE=0;
unsigned int PRIVATEADDR=0;
extern struct net_device *fanyidev;

int ip6option(int type)
{
	int i,optarray[8]={IPPROTO_HOPOPTS,IPPROTO_IPV6,IPPROTO_ROUTING,IPPROTO_FRAGMENT,
			IPPROTO_ESP,IPPROTO_AH,IPPROTO_DSTOPTS,IPPROTO_NONE};
	for(i=0;i<8;i++)
	{
		if(type==optarray[i])
			return(optarray[i]);
	}
	return(0);
}

unsigned char *getipv6uppkg(unsigned char *ippkg,unsigned char *protocol,int *uppkglen)
{
	unsigned char *ippkgpos=ippkg+40;
	struct ip6_hdr *hdr=(struct ip6_hdr *)ippkg;
	struct ip6_opthdr *opthdr;
	int ip6hdrlen;
	
	if(ip6option(hdr->ip6_nxt))
	{
		opthdr=(struct ip6_opthdr *)ippkgpos;
		while(ip6option(opthdr->nxt))
		{
			ippkgpos+=opthdr->len;
			opthdr=(struct ip6_opthdr *)ippkgpos;
		}
		if(protocol)
			*protocol=opthdr->nxt;
		ippkgpos+=opthdr->len;
	}
	else
		if(protocol)
			*protocol=hdr->ip6_nxt;
	ip6hdrlen=ippkgpos-ippkg;
	if(uppkglen)
		*uppkglen=ntohs(hdr->ip6_plen)+40-ip6hdrlen;
	return(ippkgpos);
}

unsigned short chksum(unsigned short *data,int len)
{
	unsigned short answer=0,*p=data;
	int nleft=len,sum=0;
	while(nleft>1) 
    	{ 
	        sum+=*p++;
	        nleft-=2;
	}
	if(nleft==1) 
	{
		*(unsigned char *)(&answer)=*(unsigned char *)p;
		sum+=answer;
	}
	sum=(sum>>16)+(sum&0xffff);
	sum+=(sum>>16);
	answer=~sum;
	return(answer);
}

unsigned short tcpchksum6(unsigned char *src,unsigned char *dst,unsigned char *tcppkg,int tcppkglen)
{
	struct tcp *th;
	static unsigned char *tmpbuf=NULL;
	unsigned char *bufpos;
	int tmpbuflen;

	if(tmpbuf==NULL)
        {
                tmpbuf=kmalloc(2048,GFP_KERNEL);
                if(tmpbuf==NULL)
                {
                        lprintf(0,"kmalloc error\n");
                        return(0);
                }
        }
	bufpos=tmpbuf;
	th=(struct tcp *)tcppkg;
	th->check=0;
	memcpy(bufpos,src,16);
	bufpos+=16;
	memcpy(bufpos,dst,16);
	bufpos+=16;
	*bufpos++=0;
	*bufpos++=IPPROTO_TCP;
	PKG_PUT_SHORT(tcppkglen,bufpos);
	memcpy(bufpos,tcppkg,tcppkglen);
	bufpos+=tcppkglen;
	tmpbuflen=bufpos-tmpbuf;
	return(chksum((unsigned short *)tmpbuf,tmpbuflen));
}

unsigned short tcpchksum(unsigned char *src,unsigned char *dst,unsigned char *tcppkg,int tcppkglen)
{
	struct tcp *th;
	static unsigned char *tmpbuf=NULL;
	unsigned char *bufpos;
	int tmpbuflen;

	if(tmpbuf==NULL)
        {
                tmpbuf=kmalloc(2048,GFP_KERNEL);
                if(tmpbuf==NULL)
                {
                        lprintf(0,"kmalloc error\n");
                        return(0);
                }
        }
	bufpos=tmpbuf;
	th=(struct tcp *)tcppkg;
	th->check=0;
	memcpy(bufpos,src,4);
	bufpos+=4;
	memcpy(bufpos,dst,4);
	bufpos+=4;
	*bufpos++=0;
	*bufpos++=IPPROTO_TCP;
	PKG_PUT_SHORT(tcppkglen,bufpos);
	memcpy(bufpos,tcppkg,tcppkglen);
	bufpos+=tcppkglen;
	tmpbuflen=bufpos-tmpbuf;
	return(chksum((unsigned short *)tmpbuf,tmpbuflen));
}

int ipv6_get_addr(struct net_device *dev, struct in6_addr *addr)
{
	struct inet6_dev *idev;
	int err = -EADDRNOTAVAIL;

	if ((idev = __in6_dev_get(dev)) != NULL) {
		struct inet6_ifaddr *ifp;

		read_lock_bh(&idev->lock);
		for (ifp=idev->addr_list; ifp; ifp=ifp->if_next) {
			if (ifp->scope != IFA_LINK && !(ifp->flags&IFA_F_TENTATIVE)) 
			{
				ipv6_addr_copy(addr, &ifp->addr);
				err = 0;
				break;
			}
		}
		read_unlock_bh(&idev->lock);
	}
	return err;
}

int ipv6_get_laddr(struct net_device *dev, struct in6_addr *addr)
{
	struct inet6_dev *idev;
	int err = -EADDRNOTAVAIL;

	if ((idev = __in6_dev_get(dev)) != NULL) {
		struct inet6_ifaddr *ifp;

		read_lock_bh(&idev->lock);
		for (ifp=idev->addr_list; ifp; ifp=ifp->if_next) {
			if (ifp->scope == IFA_LINK && !(ifp->flags&IFA_F_TENTATIVE)) 
			{
				ipv6_addr_copy(addr, &ifp->addr);
				err = 0;
				break;
			}
		}
		read_unlock_bh(&idev->lock);
	}
	return err;
}

int ipv4_get_addr(struct net_device *dev, unsigned int *addr)
{
	struct in_device *in_dev;
	struct in_ifaddr *ifa = NULL;

	if ((in_dev = __in_dev_get_rtnl(dev)) != NULL) 
	{
		for(ifa=in_dev->ifa_list;ifa!=NULL;ifa=ifa->ifa_next)
		{
			if(ifa->ifa_local!=0)
			{
				*addr=ifa->ifa_local;
				return(0);
			}
		}
	}
	return(-1);
}

int ip_show(struct ip *ip)
{
	if(ip==NULL)
		return 0;
	lprintf(1,"v(%u) hl(%u) tos(%u) len(%u) id(%u) off(%u) ",
		ip->ip_v,ip->ip_hl<<2,ip->ip_tos,ntohs(ip->ip_len),ntohs(ip->ip_id),ntohs(ip->ip_off));
	switch(ip->ip_p)
	{
		case IPPROTO_TCP:
			lprintf(1,"TCP ");
			break;
		case IPPROTO_UDP:
			lprintf(1,"UDP ");
			break;
		case IPPROTO_ICMP:
			lprintf(1,"ICMP ");
			break;
		default: 
			lprintf(1,"%u ",ip->ip_p);
			break;
	}
	lprintf(1,"sum(%04X) %u.%u.%u.%u --> %u.%u.%u.%u\n",ntohs(ip->ip_sum),NIPQUAD(ip->ip_src),NIPQUAD(ip->ip_dst));
	return(0);
}

int ipv6_show(struct ipv6hdr *ipv6)
{
	char dst[64],src[64];
	if(ipv6==NULL)
		return 0;
	dst[0]=src[0]=0;
	inet_ntop6((unsigned char *)&ipv6->daddr, dst, sizeof(dst)-1);
	inet_ntop6((unsigned char *)&ipv6->saddr, src, sizeof(src)-1);
	lprintf(1,"v(%u) p(%u) plen(%d) proto(%d) hlimit(%d) %s --> %s\n",
		ipv6->version,ipv6->priority,ntohs(ipv6->payload_len),ipv6->nexthdr,ipv6->hop_limit,src,dst);
	return(0);
}

int tcp_show(struct tcp *tcp)
{
	if(tcp==NULL)
		return(0);
	lprintf(1,"SPORT(%d) DPORT(%d) SEQ(%u) ACKSEQ(%u) HEADLEN(%d) FIN(%d) SYN(%d) RST(%d) PSH(%d) WINDOW(%d) SUM(%04x) URGPRT(%d)\n",
		ntohs(tcp->sport),ntohs(tcp->dport),ntohl(tcp->seq),ntohl(tcp->ack_seq),tcp->doff<<2,
		tcp->fin,tcp->syn,tcp->rst,tcp->psh,ntohs(tcp->window),ntohs(tcp->check),ntohs(tcp->urg_prt));
	return(0);
}

int udp_show(struct udp *udp)
{
	if(udp==NULL)
		return(0);
	lprintf(1,"SPORT(%d)  DPORT(%d)  LEN(%d)  SUM(%04x)\n",
		ntohs(udp->sport),ntohs(udp->dport),ntohs(udp->len),ntohs(udp->check));
	return(0);
}

int skb_show(struct sk_buff *skb)
{
	struct ipv6hdr *ipv6;
	struct tcp *tcp;
	struct udp *udp;
	if(skb==NULL)
		return(0);
	ipv6 = (struct ipv6hdr *)ipv6_hdr(skb);
	tcp=(struct tcp *)(ipv6+1);
	udp=(struct udp *)(ipv6+1);
	ipv6_show(ipv6);
	tcp_show(tcp);
	return(0);
}

void array_show(unsigned char *packp,int packlen,char hdflag,char base)
{
	int i;
	if(hdflag)
		lprintf(7,"==================== ARRAY[%d] ====================\n",packlen);
	for(i=0;i<packlen;i++)
	{
		switch(base)
		{
			case 16:
				if(i&&!(i%16))
					lprintf(7,"\n");
				lprintf(7,"%02x ",packp[i]);
				break;
			case 10:
				if(i&&!(i%16))
					lprintf(7,"\n");
				lprintf(7,"%03d ",packp[i]);
				break;
			case 2:
			{
				unsigned char n,mask=0x80;
				if(i&&!(i%4))
					lprintf(7,"\n");
				for(n=0;n<8;n++)
				{
					if(n&&!(n%4))
						lprintf(7,",");
					if(packp[i]&mask)
						lprintf(7,"1");
					else
						lprintf(7,"0");
					mask>>=1;
				}
				lprintf(7," ");
				break;
			}
			default:
				lprintf(7,"%c",packp[i]);
				break;
		}
	}
	if(base==16)
		lprintf(7,"\n");
}


static int ip4_ip6(struct net_device *dev,char *src, int len, char *dst, int include_flag)/*v4 address be translated*/
{
	struct in6_addr addr,dstaddr;
	struct iphdr *ih4 = (struct iphdr *) src; 
	struct icmphdr *icmp_hdr;  
	struct udphdr *udp_hdr;   
	struct tcphdr *tcp_hdr;

	struct ipv6hdr *ih6 = (struct ipv6hdr *) dst; 
	struct frag_hdr *ih6_frag = (struct frag_hdr *)(dst+sizeof(struct ipv6hdr));
	struct icmp6hdr *icmp6_hdr;

	int hdr_len = (int)(ih4->ihl * 4);
	int icmp_len;           
	int plen;      

	unsigned int csum;    
	int fl_csum = 0;     
	int icmperr = 1;    
	int fr_flag = 0;           
	__u16 new_tot_len; 
	__u8 new_nexthdr;    
	__u16 icmp_ptr = 0; 
	int ret;
	struct map_node *mapnode;

	if(ipv4_is_loopback(ih4->daddr)||ipv4_is_multicast(ih4->daddr)||
		ipv4_is_lbcast(ih4->daddr)||ipv4_is_zeronet(ih4->daddr)||
		ih4->daddr==ih4->saddr)
	{
		return(-1);
	}

	if(ih4->protocol==IPPROTO_UDP)
	{
		udp_hdr = (struct udphdr *)(src+hdr_len);
		if(ntohs(udp_hdr->dest)==53)
			return(-1);
	}

	ret=ipv6_get_addr(dev,&addr);
	if(ret!=0)
	{
		return(-1);
	}
	mapnode=map_search(ih4->daddr);
	if(mapnode)
		memcpy(&dstaddr,&mapnode->in6addr,sizeof(struct in6_addr));
	
	else
		return(-1);

#ifdef __BIHDEBUG__
{
	struct ip *ip=(struct ip *)ih4;
	lprintf(0,"---------- %s ----------\n",mapnode?"466":"464");
	ip_show(ip);
}
#endif

	if (ntohs(ih4->frag_off) == IP_DF || include_flag ) {
		new_tot_len = ntohs(ih4->tot_len);

		if (ih4->protocol == IPPROTO_ICMP)
			new_nexthdr = NEXTHDR_ICMP;
		else
			new_nexthdr = ih4->protocol;
	}
	else {
		/* need to add Fragment Header */
		fr_flag = 1;
		/* total length = total length from IPv4 packet +
		   length of Fragment Header */
		new_tot_len = ntohs(ih4->tot_len) + sizeof(struct frag_hdr);
		/* IPv6 Header NextHeader = NEXTHDR_FRAGMENT */
		new_nexthdr = NEXTHDR_FRAGMENT;
		/* Fragment Header NextHeader copy from IPv4 packet */
		if (ih4->protocol == IPPROTO_ICMP)
			ih6_frag->nexthdr = NEXTHDR_ICMP;
		else
			ih6_frag->nexthdr = ih4->protocol;

		/* copy frag offset from IPv4 packet */
		ih6_frag->frag_off = htons((ntohs(ih4->frag_off) & IP_OFFSET) << 3);
		/* copy MF flag from IPv4 packet */
		ih6_frag->frag_off = htons((ntohs(ih6_frag->frag_off) |
									((ntohs(ih4->frag_off) & IP_MF) >> 13)));
		/* copy Identification field from IPv4 packet */
		ih6_frag->identification = htonl(ntohs(ih4->id));
		/* reserved field initialized to zero */
		ih6_frag->reserved = 0;
	}

	ih6->version = 6;

	if (tos_ignore_flag) {
		ih6->priority = 0;
		ih6->flow_lbl[0] = 0;
	} else {
		ih6->priority = (ih4->tos & 0xf0) >> 4;
		ih6->flow_lbl[0] = (ih4->tos & 0x0f) << 4;
	}
	ih6->flow_lbl[1] = 0;
	ih6->flow_lbl[2] = 0;

	/* Hop Limit = IPv4 TTL */
	ih6->hop_limit = ih4->ttl;

	memcpy(ih6->saddr.s6_addr,addr.s6_addr,16);
	memcpy(ih6->daddr.s6_addr,dstaddr.s6_addr,16);

	plen = new_tot_len - hdr_len; /* Payload length = IPv4 total len - IPv4 header len */
	ih6->payload_len = htons(plen);
	ih6->nexthdr = new_nexthdr; /* Next Header */

	switch (ih4->protocol) {
	case IPPROTO_ICMP:
		if ( (ntohs(ih4->frag_off) & IP_OFFSET) != 0 || (ntohs(ih4->frag_off) & IP_MF) != 0 ) {
			lprintf(0,"ip4_ip6(): don't translate ICMPv4 fragments - packet dropped.\n");
			return -1;
		}

		icmp_hdr = (struct icmphdr *) (src+hdr_len); /* point to ICMPv4 header */
		csum = 0;
		icmp_len =  ntohs(ih4->tot_len) - hdr_len; /* ICMPv4 packet length */
		icmp6_hdr = (struct icmp6hdr *)(dst+sizeof(struct ipv6hdr)
									    +fr_flag*sizeof(struct frag_hdr)); /* point to ICMPv6 header */

		if (include_flag) {
			lprintf(0,"ip4_ip6(): It's included ICMPv4 in ICMPv4 Error message - packet dropped.\n");
			return -1;
		}

		/* Check ICMPv4 Type field */
		switch (icmp_hdr->type) {
		/* ICMP Error messages */
		/* Destination Unreachable (Type 3) */
		case ICMP_DEST_UNREACH:
			icmp6_hdr->icmp6_type = ICMPV6_DEST_UNREACH; /* to Type 1 */
			icmp6_hdr->icmp6_unused = 0;
			switch (icmp_hdr->code)
			{
			case ICMP_NET_UNREACH: /* Code 0 */
			case ICMP_HOST_UNREACH: /* Code 1 */
			case ICMP_SR_FAILED: /* Code 5 */
			case ICMP_NET_UNKNOWN: /* Code 6 */
			case ICMP_HOST_UNKNOWN: /* Code 7 */
			case ICMP_HOST_ISOLATED: /* Code 8 */
			case ICMP_NET_UNR_TOS: /* Code 11 */
			case ICMP_HOST_UNR_TOS: /* Code 12 */
				icmp6_hdr->icmp6_code = ICMPV6_NOROUTE; /* to Code 0 */
				break;
			case ICMP_PROT_UNREACH: /* Code 2 */
				icmp6_hdr->icmp6_type = ICMPV6_PARAMPROB; /* to Type 4 */
				icmp6_hdr->icmp6_code = ICMPV6_UNK_NEXTHDR; /* to Code 1 */
				/* Set pointer filed to 6, it's octet offset IPv6 Next Header field */
				icmp6_hdr->icmp6_pointer = htonl(6);
				break;
			case ICMP_PORT_UNREACH: /* Code 3 */
				icmp6_hdr->icmp6_code = ICMPV6_PORT_UNREACH; /* to Code 4 */
				break;
			case ICMP_FRAG_NEEDED: /* Code 4 */
				icmp6_hdr->icmp6_type = ICMPV6_PKT_TOOBIG; /* to Type 2 */
				icmp6_hdr->icmp6_code = 0;
				/* Correct MTU  */
				if (icmp_hdr->un.frag.mtu == 0)
					icmp6_hdr->icmp6_mtu = htonl(576);
				else
					icmp6_hdr->icmp6_mtu = htonl(ntohs(icmp_hdr->un.frag.mtu)+IP4_IP6_HDR_DIFF);

				break;
			case ICMP_NET_ANO: /* Code 9 */
			case ICMP_HOST_ANO: /* Code 10 */
				icmp6_hdr->icmp6_code = ICMPV6_ADM_PROHIBITED; /* to Code 1 */
				break;
			default: /* discard any other Code */
				lprintf(0,"ip4_ip6(): Unknown ICMPv4 Type %d Code %d - packet dropped.\n",
					   ICMP_DEST_UNREACH, icmp_hdr->code);
				return -1;
			}
			break;
			/* Time Exceeded (Type 11) */
		case ICMP_TIME_EXCEEDED:
			icmp6_hdr->icmp6_type = ICMPV6_TIME_EXCEED;
			icmp6_hdr->icmp6_code = icmp_hdr->code;
			break;
			/* Parameter Problem (Type 12) */
		case ICMP_PARAMETERPROB:
			icmp6_hdr->icmp6_type = ICMPV6_PARAMPROB;
			icmp6_hdr->icmp6_code = icmp_hdr->code;

			icmp_ptr = ntohs(icmp_hdr->un.echo.id) >> 8;
			switch (icmp_ptr) {
			case 0:
				icmp6_hdr->icmp6_pointer = 0;
				break;
			case 2:
				icmp6_hdr->icmp6_pointer = __constant_htonl(4);
				break;
			case 8:
				icmp6_hdr->icmp6_pointer = __constant_htonl(7);
				break;
			case 9:
				icmp6_hdr->icmp6_pointer = __constant_htonl(6);
				break;
			case 12:
				icmp6_hdr->icmp6_pointer = __constant_htonl(8);
				break;
			case 16:
				icmp6_hdr->icmp6_pointer = __constant_htonl(24);
				break;
			default:
				icmp6_hdr->icmp6_pointer = 0xffffffff;
				break;
			}
			break;
		case ICMP_ECHO:
			icmperr = 0;
			icmp6_hdr->icmp6_type = ICMPV6_ECHO_REQUEST;
			icmp6_hdr->icmp6_code = 0;
			memcpy(((char *)icmp6_hdr)+4, ((char *)icmp_hdr)+4, len - hdr_len - 4);
			break;

		case ICMP_ECHOREPLY:
			icmperr = 0;
			icmp6_hdr->icmp6_type = ICMPV6_ECHO_REPLY;
			icmp6_hdr->icmp6_code = 0;
			memcpy(((char *)icmp6_hdr)+4, ((char *)icmp_hdr)+4, len - hdr_len - 4);
			break;

		default:
			lprintf(0,"ip4_ip6(): Unknown ICMPv4 packet Type %x - packet dropped.\n", icmp_hdr->type);
			return -1;
		}

		if (icmperr) {
			if (ip4_ip6(dev,src+hdr_len+sizeof(struct icmphdr), len - hdr_len - sizeof(struct icmphdr),
						dst+sizeof(struct ipv6hdr)+fr_flag*sizeof(struct frag_hdr)
						+sizeof(struct icmp6hdr), 1) == -1) {
				lprintf(0,"ip4_ip6(): Uncorrect translation of ICMPv4 Error message - packet dropped.\n");
				return -1;
			}
			icmp_len += 20;
			plen += 20;
			ih6->payload_len = htons(plen);
		}

		icmp6_hdr->icmp6_cksum = 0;
		csum = 0;

		csum = csum_partial((u_char *)icmp6_hdr, icmp_len, csum);
		icmp6_hdr->icmp6_cksum = csum_ipv6_magic(&ih6->saddr, &ih6->daddr, icmp_len,
									         IPPROTO_ICMPV6, csum);
		break;

	case IPPROTO_TCP:
		memcpy(dst+sizeof(struct ipv6hdr)+fr_flag*sizeof(struct frag_hdr),
				   src+hdr_len, len - hdr_len);
		tcp_hdr = (struct tcphdr *)(dst+sizeof(struct ipv6hdr)+ fr_flag*sizeof(struct frag_hdr));
		if(ntohs(ih4->frag_off) == IP_DF||ih4->frag_off==0)
		{
			tcp_hdr->check = 0;
			csum = 0;
			csum = csum_partial((unsigned char *)tcp_hdr, plen - fr_flag*sizeof(struct frag_hdr), csum);
			tcp_hdr->check = csum_ipv6_magic(&ih6->saddr, &ih6->daddr, plen - fr_flag*sizeof(struct frag_hdr), IPPROTO_TCP, csum);
		}
		else if((ntohs(ih4->frag_off)&IP_OFFSET)==0&&(ntohs(ih4->frag_off)&IP_MF)!= 0)
			tcp_hdr->check = 0;
#ifdef __BIHDEBUG__
{
	struct tcp *tcp=(struct tcp *)tcp_hdr;
	ipv6_show(ih6);
	tcp_show(tcp);
}
#endif
		break;

	case IPPROTO_UDP:
		udp_hdr = (struct udphdr *)(src+hdr_len);
		if ((ntohs(ih4->frag_off) & IP_OFFSET) == 0) {
			if ((ntohs(ih4->frag_off) & IP_MF) != 0) {
				/* It's a first fragment */
				if (udp_hdr->check == 0) {
					lprintf(0,"fanyi: First fragment of UDP with zero checksum - packet droped\n");
					lprintf(0,"fanyi: addr: %x src port: %d dst port: %d\n",
						   htonl(ih4->saddr), htons(udp_hdr->source), htons(udp_hdr->dest));
					return -1;
				}
			}
			else if (udp_hdr->check == 0)
				fl_csum = 1;
		}

		udp_hdr = (struct udphdr *)(dst+sizeof(struct ipv6hdr)
									+ fr_flag*sizeof(struct frag_hdr));
		memcpy((char *)udp_hdr, src+hdr_len, len - hdr_len);

		fl_csum=1;
		if (fl_csum && (!include_flag)) {
			if(ntohs(ih4->frag_off) == IP_DF||ih4->frag_off==0)
			{
				udp_hdr->check = 0;
				csum = 0;
				csum = csum_partial((unsigned char *)udp_hdr, plen - fr_flag*sizeof(struct frag_hdr), csum);
				udp_hdr->check = csum_ipv6_magic(&ih6->saddr, &ih6->daddr, plen -
											     fr_flag*sizeof(struct frag_hdr), IPPROTO_UDP, csum);
			}
			else if((ntohs(ih4->frag_off)&IP_OFFSET)==0&&(ntohs(ih4->frag_off)&IP_MF)!= 0)
				udp_hdr->check = 0;
#ifdef __BIHDEBUG__
{
	struct udp *udp=(struct udp *)udp_hdr;
	ipv6_show(ih6);
	udp_show(udp);
	//array_show((unsigned char *)udp_hdr,len-hdr_len,1,16);
}
#endif
		}
		break;

	/* Discard packets with any other protocol */
	default:
		lprintf(0,"ip4_ip6(): Unknown upper protocol - packet dropped.\n");
		return -1;
	}
	return 0;
}

static int ip6_ip4(struct net_device *dev,char *src, int len, char *dst, int include_flag)/*v6 address be translated*/
{
	unsigned int srcaddr,dstaddr;
	struct in6_addr addr6;
	struct ipv6hdr *ip6_hdr=(struct ipv6hdr *)src;
	struct iphdr *ip_hdr=(struct iphdr *)dst; 
	int opts_len = 0;  
	int icmperr = 1;   
	int ntot_len = 0; 
	int real_len;
	int len_delta;
	int ip6_payload_len;
	int inc_opts_len = 0; 
	__u8 next_hdr;  
	int ret;
	int addr_type;
	struct map_node *mapnode;
	struct udphdr *udp_hdr;
	unsigned char proto=0;

	addr_type=ipv6_addr_type(&ip6_hdr->daddr);
	if (addr_type&IPV6_ADDR_MULTICAST||
			addr_type&IPV6_ADDR_LOOPBACK||
			addr_type&IPV6_ADDR_LINKLOCAL||
			addr_type&IPV6_ADDR_SITELOCAL)
		return(-1);
	addr_type=ipv6_addr_type(&ip6_hdr->saddr);
	if(addr_type&IPV6_ADDR_LINKLOCAL||
			addr_type&IPV6_ADDR_SITELOCAL)
		return(-1);

	udp_hdr=(struct udphdr *)getipv6uppkg(src,&proto,NULL);
	if(proto==NEXTHDR_UDP&&ntohs(udp_hdr->source)==53)
		return(-1);

	ret=ipv6_get_addr(dev,&addr6);
	if(ret!=0)
	{
		return(-1);
	}
	ret=ipv4_get_addr(dev,&dstaddr);
	if(ret!=0)
	{
		return(-1);
	}
	if(memcmp(&ip6_hdr->daddr,&addr6,sizeof(struct in6_addr))!=0)
	{
/*
#ifdef __BIHDEBUG__
		char dstbuf[64],locbuf[64];
		inet_ntop6((unsigned char *)&ip6_hdr->daddr.s6_addr, dstbuf, sizeof(dstbuf)-1);
		inet_ntop6((unsigned char *)&addr6.s6_addr, locbuf, sizeof(locbuf)-1);
		lprintf(0,"Pkg dst(%s) will be forward\n",dstbuf,locbuf);
#endif
*/
		return -1;
	}	
	
	mapnode=map_find(ip6_hdr->saddr);
	if(mapnode)
		srcaddr=mapnode->id;
	
	else
	{
		srcaddr=pool_addr_assign(&ip6_hdr->saddr);
		if(srcaddr==0)
		{
			lprintf(0,"pool_addr_assign error\n");
			return(-1);
		}
		mapnode=(struct map_node *)1;
	}
	
#ifdef __BIHDEBUG__
	lprintf(0,"---------- %s ----------\n",mapnode?"466":"464");
	ipv6_show(ip6_hdr);
#endif
	if ( (len_delta = len - sizeof(struct ipv6hdr)) >= 0)
	{
		real_len = sizeof(struct iphdr);

		ip_hdr->frag_off = 0;
		ip_hdr->id = 0;

		next_hdr = ip6_hdr->nexthdr;

		if (next_hdr == NEXTHDR_HOP) {
			if ( (len_delta - sizeof(struct ipv6_opt_hdr)) >= 0)
			{
				struct ipv6_opt_hdr *ip6h =
					(struct ipv6_opt_hdr *)(src+sizeof(struct ipv6hdr) + opts_len);
				if ( (len_delta -= ip6h->hdrlen*8 + 8) >= 0)
				{
					opts_len += ip6h->hdrlen*8 + 8;
					next_hdr = ip6h->nexthdr;
				}
				else
				{
					lprintf(0,"ip6_ip4(): hop_by_hop header error, packet droped\n");
					return -1;
				}
			}
		}

		if (len_delta > 0)
		{
			while(next_hdr != NEXTHDR_ICMP && next_hdr != NEXTHDR_TCP
				  && next_hdr != NEXTHDR_UDP)
			{
				/* Destination options header */
				if (next_hdr == NEXTHDR_DEST)
				{
					if ( (len_delta - sizeof(struct ipv6_opt_hdr)) >= 0)
					{
						struct ipv6_opt_hdr *ip6d =
							(struct ipv6_opt_hdr *)(src + sizeof(struct ipv6hdr) + opts_len);
						if ( (len_delta -= ip6d->hdrlen*8 + 8) >= 0)
						{
							opts_len += ip6d->hdrlen*8 + 8;
							next_hdr = ip6d->nexthdr;
						}
					}
					else
					{
						lprintf(0,"ip6_ip4(): destination header error, packet droped");
						return -1;
					}
				}
				else if (next_hdr == NEXTHDR_ROUTING)
				{
					if ( (len_delta - sizeof(struct ipv6_rt_hdr)) >= 0)
					{
						struct ipv6_rt_hdr *ip6rt =
							(struct ipv6_rt_hdr *)(src+sizeof(struct ipv6hdr) + opts_len);
						if (ip6rt->segments_left != 0) {
							lprintf(0,"ip6_ip4(): routing header type != 0\n");
							return -1;
						}
						if ( (len_delta -= ip6rt->hdrlen*8 + 8) >= 0)
						{
							opts_len += ip6rt->hdrlen*8 + 8;
							next_hdr = ip6rt->nexthdr;
						}
						else
						{
							lprintf(0,"ip6_ip4(): routing header error, packet droped");
							return -1;
						}
					}
				}
				else if (next_hdr == NEXTHDR_FRAGMENT)
				{
					if ( (len_delta -= sizeof(struct frag_hdr)) >= 0)
					{
						struct frag_hdr *ip6f =
							(struct frag_hdr *)(src+sizeof(struct ipv6hdr)+opts_len);

						opts_len += sizeof(struct frag_hdr);      /* Frag Header Length = 8 */
						ip_hdr->id = htons(ntohl(ip6f->identification)); /* ID field */
						ip_hdr->frag_off = htons((ntohs(ip6f->frag_off) & IP6F_OFF_MASK) >> 3);
										                                   /* fragment offset */
						ip_hdr->frag_off = htons(ntohs(ip_hdr->frag_off) |
										         ((ntohs(ip6f->frag_off) & IP6F_MORE_FRAG) << 13));
										                                  /* more fragments flag */
						next_hdr = ip6f->nexthdr;
					}
					else
					{
						lprintf(0,"ip6_ip4(): fragment header error, packet droped");
						return -1;
					}
				}
				else if (next_hdr == NEXTHDR_NONE)
				{
					break;
				}
				else if (next_hdr == NEXTHDR_ESP || next_hdr == NEXTHDR_AUTH)
				{
					lprintf(0,"ip6_ip4(): cannot translate AUTH or ESP extention header, packet dropped\n");
					return -1;
				}
				else if (next_hdr == NEXTHDR_IPV6)
				{
					lprintf(0,"ip6_ip4(): cannot translate IPv6-IPv6 packet, packet dropped\n");
					return -1;
				}
				else if (next_hdr == 0)
				{
					lprintf(0,"ip6_ip4(): NEXTHDR in extention header = 0, packet dropped\n");
					return -1;
				}
				else
				{
					lprintf(0,"ip6_ip4(): cannot translate extention header = %d, packet dropped\n", next_hdr);
					return -1;
				}
			}
		}
	}
	else
	{
	   lprintf(0,"ip6_ip4(): error packet len, packet dropped.\n");
	   return -1;
	}

	ip_hdr->version = IPVERSION;
	ip_hdr->ihl = 5;

	if (tos_ignore_flag)
		ip_hdr->tos = 0;
	else {
		ip_hdr->tos = ip6_hdr->priority << 4;
		ip_hdr->tos = ip_hdr->tos | (ip6_hdr->flow_lbl[0] >> 4);
	}

	ip6_payload_len = ntohs(ip6_hdr->payload_len);

	if (ip6_payload_len == 0)
		ntot_len = 0;
	else
		ntot_len = ip6_payload_len + IP4_IP6_HDR_DIFF - opts_len;

	ip_hdr->tot_len = htons(ntot_len);
	ip_hdr->ttl = ip6_hdr->hop_limit;
	ip_hdr->protocol = next_hdr;

	ip_hdr->saddr = srcaddr;
	ip_hdr->daddr = dstaddr;
	
	ip_hdr->check = 0;
	ip_hdr->check = ip_fast_csum((unsigned char *)ip_hdr, ip_hdr->ihl);

	if (len_delta > 0)
	{
		if (next_hdr == NEXTHDR_ICMP)
		{
			struct icmp6hdr *icmp6_hdr;
			struct icmphdr *icmp_hdr;

			if ((len_delta -= sizeof(struct icmp6hdr)) >= 0)
			{
				icmp6_hdr = (struct icmp6hdr *)(src + sizeof(struct ipv6hdr) + opts_len);
				icmp_hdr = (struct icmphdr *)(dst + sizeof(struct iphdr));

				real_len += len_delta + sizeof(struct icmphdr);

				ip_hdr->protocol = IPPROTO_ICMP;

				if (include_flag) {
					if (icmp6_hdr->icmp6_type != ICMPV6_ECHO_REQUEST)
					{
						lprintf(0,"ip6_ip4(): included ICMPv6 in ICMPv6 Error message, packet dropped\n");
						return -1;
					}
				}

				switch (icmp6_hdr->icmp6_type)
				{
				case ICMPV6_DEST_UNREACH: /* Type 1 */
					icmp_hdr->type = ICMP_DEST_UNREACH; /* to Type 3 */
					icmp_hdr->un.echo.id = 0;
					icmp_hdr->un.echo.sequence = 0;
					switch (icmp6_hdr->icmp6_code)
					{
					case ICMPV6_NOROUTE: /* Code 0 */
					case ICMPV6_NOT_NEIGHBOUR: /* Code 2 */
					case ICMPV6_ADDR_UNREACH: /* Code 3  */
						icmp_hdr->code = ICMP_HOST_UNREACH; /* To Code 1 */
						break;
					case ICMPV6_ADM_PROHIBITED: /* Code 1 */
						icmp_hdr->code = ICMP_HOST_ANO; /* To Code 10 */
						break;
					case ICMPV6_PORT_UNREACH: /* Code 4 */
						icmp_hdr->code = ICMP_PORT_UNREACH; /* To Code 3 */

						break;
					default:            /* discard any other codes */
						lprintf(0,"ip6_ip4(): Unknown ICMPv6 Type %d Code %d - packet dropped.\n",
							   ICMPV6_DEST_UNREACH, icmp6_hdr->icmp6_code);
						return -1;
					}
					break;
				case ICMPV6_PKT_TOOBIG: /* Type 2 */
					icmp_hdr->type = ICMP_DEST_UNREACH; /* to Type 3  */
					icmp_hdr->code = ICMP_FRAG_NEEDED; /*  to Code 4 */
					icmp_hdr->un.frag.mtu = (__u16) icmp6_hdr->icmp6_mtu;
					break;
				case ICMPV6_TIME_EXCEED:
					icmp_hdr->type = ICMP_TIME_EXCEEDED; /* to Type 11 */
					icmp_hdr->code = icmp6_hdr->icmp6_code; /* Code unchanged */
					break;
				case ICMPV6_PARAMPROB:
					switch (icmp6_hdr->icmp6_code) {
					case ICMPV6_UNK_NEXTHDR: /* Code 1 */
						icmp_hdr->type = ICMP_DEST_UNREACH; /* to Type 3 */
						icmp_hdr->code = ICMP_PROT_UNREACH; /* to Code 2 */
						break;
					default: /* if Code != 1 */
						icmp_hdr->type = ICMP_PARAMETERPROB; /* to Type 12 */
						icmp_hdr->code = 0; /* to Code 0 */
						switch (ntohl(icmp6_hdr->icmp6_pointer))
						{
						case 0: /* IPv6 Version -> IPv4 Version */
							icmp_hdr->un.echo.id = 0;
							break;
						case 4: /* IPv6 PayloadLength -> IPv4 Total Length */
							icmp_hdr->un.echo.id = 0x0002; /* 2 */
							break;
						case 6: /* IPv6 Next Header-> IPv4 Protocol */
							icmp_hdr->un.echo.id = 0x0009; /* 9 */
							break;
						case 7: /* IPv6 Hop Limit -> IPv4 TTL */
							icmp_hdr->un.echo.id = 0x0008; /* 8 */
							break;
						case 8: /* IPv6 Src addr -> IPv4 Src addr */
							icmp_hdr->un.echo.id = 0x000c; /* 12 */
							break;
						case 24: /* IPv6 Dst addr -> IPv4 Dst addr*/
							icmp_hdr->un.echo.id = 0x0010; /* 16 */
							break;
						default: /* set all ones in other cases */
							icmp_hdr->un.echo.id = 0xff;
							break;
						}
						break;
					}
					break;

				case ICMPV6_ECHO_REQUEST:
					icmperr = 0;        /* not error ICMP message */
					icmp_hdr->type = ICMP_ECHO; /* to Type 8 */
					icmp_hdr->code = 0; /* to Code 0 */
					icmp_hdr->un.echo.id = icmp6_hdr->icmp6_identifier;
					icmp_hdr->un.echo.sequence = icmp6_hdr->icmp6_sequence;
					if (len_delta > 0)
						memcpy(((char *)icmp_hdr) + sizeof(struct icmphdr),
							   ((char *)icmp6_hdr) + sizeof(struct icmp6hdr), len_delta);
					break;
				case ICMPV6_ECHO_REPLY:
					icmperr = 0;        /* not error ICMP message */
					icmp_hdr->type = ICMP_ECHOREPLY; /* to Type 0 */
					icmp_hdr->code = 0; /* to Code 0 */
					icmp_hdr->un.echo.id = icmp6_hdr->icmp6_identifier;
					icmp_hdr->un.echo.sequence = icmp6_hdr->icmp6_sequence;
					if (len_delta > 0)
						memcpy(((char *)icmp_hdr) + sizeof(struct icmphdr),
							   ((char *)icmp6_hdr) + sizeof(struct icmp6hdr), len_delta);
					break;
				default:
					lprintf(0,"ip6_ip4(): unknown ICMPv6 Type %d, packet dropped.\n", icmp6_hdr->icmp6_type);
					return -1;
				}

				if (icmperr)
				{
					if (len_delta >= sizeof(struct ipv6hdr))
					{
						if((inc_opts_len = ip6_ip4(dev,(char *)icmp6_hdr + sizeof(struct icmp6hdr), len_delta,
										           (char *)icmp_hdr + sizeof(struct icmphdr), 1)) == -1) {
							lprintf(0,"ip6_ip4(): incorrect translation of ICMPv6 Error message, packet dropped\n");
							return -1;
						}
						if (ntot_len != 0)
							ip_hdr->tot_len = htons(ntot_len - inc_opts_len - IP4_IP6_HDR_DIFF);
						real_len = real_len - inc_opts_len - IP4_IP6_HDR_DIFF;
					}
					else if (len_delta > 0)
					{
						memcpy(((char *)icmp_hdr) + sizeof(struct icmphdr),
							   ((char *)icmp6_hdr) + sizeof(struct icmp6hdr), len_delta);
					}
				}

				ip_hdr->check = 0;
				ip_hdr->check = ip_fast_csum((unsigned char *)ip_hdr, ip_hdr->ihl);

				if (ntot_len != 0)
				{
					icmp_hdr->checksum = 0;
					icmp_hdr->checksum = ip_compute_csum((unsigned char *)icmp_hdr, ntohs(ip_hdr->tot_len)
										             - sizeof(struct iphdr));
				}
			}
			else
			{
				lprintf(0,"ip6_ip4(): error length ICMP packet, packet dropped.\n");
				return -1;
			}

		}
		else {
			unsigned int csum;
			struct udphdr *udp_hdr;
			struct tcphdr *tcp_hdr;
			real_len += len_delta;
			memcpy(dst+sizeof(struct iphdr), src + sizeof(struct ipv6hdr) + opts_len, len_delta);
			if (next_hdr == NEXTHDR_TCP)
			{
				tcp_hdr = (struct tcphdr *)(dst+sizeof(struct iphdr));
				if(ntohs(ip_hdr->frag_off) == IP_DF||ip_hdr->frag_off==0)
				{
					tcp_hdr->check = 0;
					csum = 0;
					csum = csum_partial((unsigned char *)tcp_hdr, len_delta, csum);
					tcp_hdr->check = csum_tcpudp_magic(ip_hdr->saddr, ip_hdr->daddr, len_delta, IPPROTO_TCP, csum);
				}
				else if((ntohs(ip_hdr->frag_off)&IP_OFFSET)==0&&(ntohs(ip_hdr->frag_off)&IP_MF)!= 0)
					tcp_hdr->check = 0;
#ifdef __BIHDEBUG__
{
	struct tcp *tcp=(struct tcp *)tcp_hdr;
	struct ip *ip=(struct ip *)ip_hdr;
	ip_show(ip);
	tcp_show(tcp);
}
#endif
			}
			else if (next_hdr == NEXTHDR_UDP)
			{
				udp_hdr = (struct udphdr *)(dst+sizeof(struct iphdr));
				if(ntohs(ip_hdr->frag_off) == IP_DF||ip_hdr->frag_off==0)
				{
					udp_hdr->check = 0;
					csum = 0;
					csum = csum_partial((unsigned char *)udp_hdr, len_delta, csum);
					udp_hdr->check = csum_tcpudp_magic(ip_hdr->saddr, ip_hdr->daddr, len_delta, IPPROTO_UDP, csum);
				}
				else if((ntohs(ip_hdr->frag_off)&IP_OFFSET)==0&&(ntohs(ip_hdr->frag_off)&IP_MF)!= 0)
					udp_hdr->check = 0;
#ifdef __BIHDEBUG__
{
	struct udp *udp=(struct udp *)udp_hdr;
	struct ip *ip=(struct ip *)ip_hdr;
	ip_show(ip);
	udp_show(udp);
	//array_show(dst+sizeof(struct iphdr), len_delta,1,16);
}
#endif
			}
		}
	}
	if (include_flag)
		return opts_len;
	else
		return real_len;
}

static int ip4_fragment(struct sk_buff *skb, int len, int hdr_len, struct net_device *dev)
{
	struct sk_buff *skb2 = NULL;       /* pointer to new struct sk_buff for transleded packet */
	char buff[FRAG_BUFF_SIZE+hdr_len]; /* buffer to form new fragment packet */
	char *cur_ptr = skb->data+hdr_len; /* pointter to current packet data with len = frag_len */
	struct iphdr *ih4 = (struct iphdr *) skb->data;
	struct iphdr *new_ih4 = (struct iphdr *) buff; /* point to new IPv4 hdr */
	struct ethhdr *new_eth_h;   /* point to ether hdr, need to set hard header data in fragment */
	int data_len = len - hdr_len; /* origin packet data len */
	int rest_len = data_len;    /* rest data to fragment */
	int frag_len = 0;           /* current fragment len */
	int last_frag = 0;          /* last fragment flag, if = 1, it's last fragment */
	int flag_last_mf = 0;
	int ret;
	__u16 new_id = 0;           /* to generate identification field */
	__u16 frag_offset = 0;      /* fragment offset */
	unsigned int csum;
	unsigned short udp_len;

	if ((ntohs(ih4->frag_off) & IP_MF) == 0 )
		/* it's a case we'll clear MF flag in our last packet */
		flag_last_mf = 1;

	if (ih4->protocol == IPPROTO_UDP) {
		if ( (ntohs(ih4->frag_off) & IP_OFFSET) == 0) {
			struct udphdr *udp_hdr = (struct udphdr *)((char *)ih4 + hdr_len);
			if (!flag_last_mf) {
				if (udp_hdr->check == 0) {
					/* it's a first fragment with ZERO checksum and we drop packet */
					printk("fanyi: First fragment of UDP with zero checksum - packet droped\n");
					printk("fanyi: addr: %x src port: %d dst port: %d\n",
						   htonl(ih4->saddr), htons(udp_hdr->source), htons(udp_hdr->dest));
					return -1;
				}
			}
			else if (udp_hdr->check == 0) {
				/* Calculate UDP checksum only if it's not fragment */
				udp_len = ntohs(udp_hdr->len);
				csum = 0;
				csum = csum_partial((unsigned char *)udp_hdr, udp_len, csum);
				udp_hdr->check = csum_tcpudp_magic(ih4->saddr, ih4->daddr, udp_len, IPPROTO_UDP, csum);
			}
		}
	}
	frag_offset = ntohs(ih4->frag_off) & IP_OFFSET;
	new_id = ih4->id;
	while(1) {
		if (rest_len <= FRAG_BUFF_SIZE) {
			/* it's last fragmen */
			frag_len = rest_len; /* rest data */
			last_frag = 1;
		}
		else
			frag_len = FRAG_BUFF_SIZE;
		/* copy IP header to buffer */
		memcpy(buff, skb->data, hdr_len);
		/* copy data to buffer with len = frag_len */
		memcpy(buff + hdr_len, cur_ptr, frag_len);
		/* set id field in new IPv4 header*/
		new_ih4->id = new_id;
		/* is it last fragmet */
		if(last_frag && flag_last_mf)
			/* clear MF flag */
			new_ih4->frag_off = htons(frag_offset & (~IP_MF));
		else
			/* set MF flag */
			new_ih4->frag_off = htons(frag_offset | IP_MF);
		/* change packet total length */
		new_ih4->tot_len = htons(frag_len+hdr_len);
		/* rebuild the header checksum (IP needs it) */
		new_ih4->check = 0;
		new_ih4->check = ip_fast_csum((unsigned char *)new_ih4,new_ih4->ihl);
		/* Allocate new sk_buff to compose translated packet */
		skb2 = dev_alloc_skb(frag_len+hdr_len+dev->hard_header_len+IP4_IP6_HDR_DIFF+IP6_FRAGMENT_SIZE);
		if (!skb2) {
			printk(KERN_DEBUG "%s: alloc_skb failure - packet dropped.\n", dev->name);
			dev_kfree_skb(skb2);
			return -1;
		}
		skb_put(skb2, frag_len+hdr_len+dev->hard_header_len+IP4_IP6_HDR_DIFF+IP6_FRAGMENT_SIZE);
		new_eth_h = (struct ethhdr *)skb2->data;
		new_eth_h->h_proto = htons(ETH_P_IPV6);
		skb_reset_mac_header(skb2);
		skb_pull(skb2, dev->hard_header_len);
		skb_reset_network_header(skb2);
		skb2->protocol = htons(ETH_P_IPV6);
		/* call translation function */
		if ( ip4_ip6(dev,buff, frag_len+hdr_len, skb2->data, 0) == -1) {
//			lprintf(0,"ip4_ip6 error\n");
			dev_kfree_skb(skb2);
			return -1;
		}
		/*
		 * Set needed fields in new sk_buff
		 */
		skb2->dev = dev;
		skb2->ip_summed = CHECKSUM_UNNECESSARY;
		skb2->pkt_type = PACKET_HOST;

		/* send packet to upper layer */
//		ret=ipv6_rcv(skb2,dev,NULL,NULL);
		ret=netif_rx(skb2);
		if(ret!=0)
		{
			lprintf(0,"ipv6_rcv error\n");
			dev_kfree_skb(skb2);
			return -1;
		}
		/* exit if it was last fragment */
		if (last_frag)
			break;

		/* correct current data pointer */
		cur_ptr += frag_len;
		/* rest data len */
		rest_len -= frag_len;
		/* current fragment offset */
		frag_offset = (frag_offset*8 + frag_len)/8;
	}
	return 0;
}

int fanyi_xmit(struct sk_buff *skb,struct net_device *dev)
{
	struct sk_buff *skb2 = NULL, *skb3=NULL;
	int ret,new_packet_len,len,skb_delta = 0;
	static char *new_packet_buff;
	struct ethhdr *eth_h;
	struct in6_addr addr;

	if (skb == NULL || dev==NULL)
	{
		return -1;
	}
/*
	if(skb->dev==fanyidev||BIHMODE!=1)
		return(-1);
*/
	ret=ipv6_get_addr(dev,&addr);
	if(ret!=0)
		return(-1);

	if(new_packet_buff==NULL)
	{
		new_packet_buff=kmalloc(2048,GFP_KERNEL);
		if(new_packet_buff==NULL)
		{
			lprintf(0,"kmalloc error\n");
			return(-1);
		}
	}

	dev->trans_start = jiffies;
	if (ntohs(skb->protocol) == ETH_P_IP) 
	{
		int hdr_len;  
		int data_len;    
		struct iphdr *ih4; 
		struct icmphdr *icmp_hdr; 

		ih4 = (struct iphdr *)skb->data; /* point to incoming packet's IPv4 header */
		if (skb->len != ntohs(ih4->tot_len)) {
			lprintf(0,"skb error\n");
			return -1;
		}
		len = skb->len; 
		hdr_len = (int)(ih4->ihl * 4);
		data_len = len - hdr_len;

//		if ( (ntohs(ih4->frag_off) & IP_DF) == 0 ) 
		{
			if ( data_len > FRAG_BUFF_SIZE ) 
//			if ( data_len > 1440 ) 
			{
				if(skb_is_nonlinear(skb))
				{
					skb3=skb_copy(skb, GFP_ATOMIC);
					if(skb3)
					{
						skb3->sk=skb->sk;
					}
				}
				else
					skb3=skb;
				len = skb3->len; 
				ret=ip4_fragment(skb3, len, hdr_len, dev);
				if(skb3!=skb)
					dev_kfree_skb(skb3);
				if(ret==0)
					dev_kfree_skb(skb);
				return ret;
			}
		}
		if ( ntohs(ih4->frag_off) == IP_DF )
			skb_delta = IP4_IP6_HDR_DIFF;
		else
			skb_delta = IP4_IP6_HDR_DIFF + IP6_FRAGMENT_SIZE;
		if ( ih4->protocol == IPPROTO_ICMP) {
			icmp_hdr = (struct icmphdr *) (skb->data+hdr_len);
			if ( icmp_hdr->type != ICMP_ECHO && icmp_hdr->type != ICMP_ECHOREPLY) {
				skb_delta += IP4_IP6_HDR_DIFF;
			}
		}
		skb2 = dev_alloc_skb(len+dev->hard_header_len+skb_delta);
		if (!skb2) {
			lprintf(0,"dev_alloc_skb error\n");
			return -1;
		}
		skb_put(skb2, len+dev->hard_header_len+skb_delta);
		eth_h = (struct ethhdr *)skb2->data;
		eth_h->h_proto = htons(ETH_P_IPV6);
		skb_reset_mac_header(skb2);
		skb_pull(skb2,dev->hard_header_len);
//		skb2->h.raw = skb2->nh.raw = skb2->data;
		skb_reset_network_header(skb2);
		skb2->protocol = htons(ETH_P_IPV6);

		if(skb_is_nonlinear(skb))
		{
			skb3=skb_copy(skb, GFP_ATOMIC);
			if(skb3)
			{
				skb3->sk=skb->sk;
			}
		}
		else
			skb3=skb;
		if (ip4_ip6(dev,skb3->data, len, skb2->data, 0) == -1 ) {
			if(skb3!=skb)
				dev_kfree_skb(skb3);
			dev_kfree_skb(skb2);
			return -1;
		}
		if(skb3!=skb)
			dev_kfree_skb(skb3);
		skb2->dev = fanyidev;
	}
	else if (ntohs(skb->protocol) == ETH_P_IPV6) 
	{
		len = skb->len;
		if(skb_is_nonlinear(skb))
		{
			skb3=skb_copy(skb, GFP_ATOMIC);
			if(skb3)
			{
				skb3->sk=skb->sk;
			}
		}
		else
			skb3=skb;
		new_packet_len = ip6_ip4(dev,skb3->data, len, new_packet_buff, 0);
		if(new_packet_len==-1)
		{
			if(skb3!=skb)
				dev_kfree_skb(skb3);
			return(-1);
		}
		if(skb3!=skb)
			dev_kfree_skb(skb3);
		skb2 = dev_alloc_skb(new_packet_len + dev->hard_header_len);
		if (!skb2) 
		{
			lprintf(0,"dev_alloc_skb error\n");
			return(-1);
		}
		skb_put(skb2, new_packet_len + dev->hard_header_len);
		eth_h = (struct ethhdr *)skb2->data;
		eth_h->h_proto = htons(ETH_P_IP);
		skb_reset_mac_header(skb2);
		skb_pull(skb2, dev->hard_header_len);
		memcpy(skb2->data, new_packet_buff, new_packet_len);
//		skb2->h.raw = skb2->nh.raw = skb2->data;
		skb_reset_network_header(skb2);
		skb_reset_transport_header(skb2);
		skb2->mac_len = skb2->network_header - skb2->mac_header;
		skb2->protocol = htons(ETH_P_IP);
		skb2->dev=skb->dev;
	}
	else {
		return(-1);
	}
	
	skb2->pkt_type = PACKET_HOST;
	skb2->dev = dev;
	skb2->ip_summed = CHECKSUM_UNNECESSARY;
	if (skb->sk)
		skb_set_owner_w(skb2, skb->sk);

	ret=netif_rx(skb2);
/*	
	if (ntohs(skb2->protocol) == ETH_P_IP) 
		ret=ip_rcv(skb2,dev,NULL,NULL);
	else
		ret=ipv6_rcv(skb2,dev,NULL,NULL);
*/
	if(ret!=0)
		printk("bih: ip_rcv|ip6_rcv error(ret=%d)\n",ret);
	else
		kfree_skb(skb);
	return(ret);
}
